/** @file

 @copyright
  INTEL CONFIDENTIAL
  Copyright 2012 - 2017 Intel Corporation.

  The source code contained or described herein and all documents related to the
  source code ("Material") are owned by Intel Corporation or its suppliers or
  licensors. Title to the Material remains with Intel Corporation or its suppliers
  and licensors. The Material may contain trade secrets and proprietary and
  confidential information of Intel Corporation and its suppliers and licensors,
  and is protected by worldwide copyright and trade secret laws and treaty
  provisions. No part of the Material may be used, copied, reproduced, modified,
  published, uploaded, posted, transmitted, distributed, or disclosed in any way
  without Intel's prior express written permission.

  No license under any patent, copyright, trade secret or other intellectual
  property right is granted to or conferred upon you by disclosure or delivery
  of the Materials, either expressly, by implication, inducement, estoppel or
  otherwise. Any license under such intellectual property rights must be
  express and approved by Intel in writing.

  Unless otherwise agreed by Intel in writing, you may not remove or alter
  this notice or any other notice embedded in Materials by Intel or
  Intel's suppliers or licensors in any way.

  This file contains a 'Sample Driver' and is licensed as such under the terms
  of your license agreement with Intel or your vendor. This file may be modified
  by the user, subject to the additional terms of the license agreement.

@par Specification Reference:
**/

#include <Library/I2cAccessLib.h>

EFI_STATUS
I2cWriteRead (
  IN UINTN  MmioBase,
  IN UINT8  SlaveAddress,
  IN UINT8  WriteLength,
  IN UINT8  *WriteBuffer,
  IN UINT8  ReadLength,
  IN UINT8  *ReadBuffer,
  IN UINT64  TimeBudget
  //TODO: add Speed parameter
  )
{
  UINT8 ReadsNeeded = ReadLength;
  UINT64 CutOffTime;

  if ((WriteLength == 0 && ReadLength == 0) ||
      (WriteLength != 0 && WriteBuffer == NULL) ||
      (ReadLength != 0 && ReadBuffer == NULL) ) {
    DEBUG ((DEBUG_ERROR, "I2cWR Invalid Parameters\n"));
    return EFI_INVALID_PARAMETER;
  }

  //
  // Sanity checks to verify the I2C controller is alive
  // Conveniently, ICON register's values of 0 or FFFFFFFF indicate
  // I2c controller is out-of-order: either disabled, in D3 or in reset.
  //
  if (MmioRead32(MmioBase+R_IC_CON) == 0xFFFFFFFF || MmioRead32(MmioBase+R_IC_CON) == 0x0) {
    DEBUG ((DEBUG_ERROR, "I2cWR Device Error\n"));
    return EFI_DEVICE_ERROR;
  }

  MmioWrite32(MmioBase+R_IC_ENABLE, 0x0);
  MmioRead32(MmioBase+0x40);
  MmioRead32(MmioBase+R_IC_CLR_TX_ABRT);
  MmioWrite32(MmioBase+R_IC_SDA_HOLD, 0x001C001C);
  //
  // Set I2C Bus Speed at 400 kHz for GPIO Expander
  //
  MmioWrite32(MmioBase + R_IC_FS_SCL_HCNT, 128);
  MmioWrite32(MmioBase + R_IC_FS_SCL_LCNT, 160);
  MmioWrite32(MmioBase + R_IC_TAR, SlaveAddress);
  MmioWrite32(MmioBase + R_IC_CON, B_IC_MASTER_MODE | V_IC_SPEED_FAST | B_IC_RESTART_EN | B_IC_SLAVE_DISABLE );
  MmioWrite32(MmioBase+R_IC_ENABLE, 0x1);
  CutOffTime = AsmReadTsc() + TimeBudget;

  while ( (MmioRead32(MmioBase+R_IC_ENABLE_STATUS) & 1)==0 ) {
    if (AsmReadTsc() > CutOffTime) {
      DEBUG ((DEBUG_ERROR, "I2cWR timeout\n"));
      return EFI_TIMEOUT;
    }
  }

  while(1) {
    if(MmioRead32(MmioBase+R_IC_INTR_STAT) & B_IC_INTR_TX_ABRT) {
      DEBUG ((DEBUG_ERROR, "I2cWR Transfer aborted, reason = 0x%08x\n",MmioRead32(MmioBase+R_IC_TX_ABRT_SOURCE)));
      MmioRead32(MmioBase+R_IC_CLR_TX_ABRT);
      MmioAnd32(MmioBase+R_IC_ENABLE, 0xFFFFFFFE);
      while ( (MmioRead32(MmioBase+R_IC_ENABLE_STATUS) & 1)==1 ) {}
      return EFI_DEVICE_ERROR;
    }
    if (MmioRead32(MmioBase+R_IC_STATUS) & B_IC_STATUS_TFNF) {
      if (WriteLength > 1) {
        MmioWrite32(MmioBase+R_IC_DATA_CMD, *WriteBuffer);
        WriteBuffer++;
        WriteLength--;
      } else if (WriteLength==1 && ReadLength != 0) {
        MmioWrite32(MmioBase+R_IC_DATA_CMD, *WriteBuffer);
        WriteBuffer++;
        WriteLength--;
      } else if (WriteLength==1 && ReadLength == 0) {
        MmioWrite32(MmioBase+R_IC_DATA_CMD, *WriteBuffer | B_IC_CMD_STOP);
        WriteBuffer++;
        WriteLength--;
      } else if (ReadLength > 1) {
        MmioWrite32(MmioBase+R_IC_DATA_CMD, B_IC_CMD_READ);
        ReadLength--;
      } else if (ReadLength == 1) {
        MmioWrite32(MmioBase+R_IC_DATA_CMD, B_IC_CMD_READ|B_IC_CMD_STOP);
        ReadLength--;
      }
    }

    if (ReadsNeeded) {
      if (MmioRead32(MmioBase+R_IC_STATUS) & B_IC_STATUS_RFNE) {
        *ReadBuffer = (UINT8)MmioRead32(MmioBase+R_IC_DATA_CMD);
        ReadBuffer++;
        ReadsNeeded--;
      }
    }
    if (WriteLength==0 && ReadsNeeded==0 && !(MmioRead32(MmioBase+R_IC_STATUS)&B_IC_STATUS_ACTIVITY)) {
      MmioAnd32(MmioBase+R_IC_ENABLE, 0xFFFFFFFE);
      while ( (MmioRead32(MmioBase+R_IC_ENABLE_STATUS) & 1)==1 ) {}
      DEBUG ((DEBUG_INFO, "I2cWR success\n"));
      return EFI_SUCCESS;
    }
    if (AsmReadTsc() > CutOffTime) {
      MmioAnd32(MmioBase+R_IC_ENABLE, 0xFFFFFFFE);
      while ( (MmioRead32(MmioBase+R_IC_ENABLE_STATUS) & 1)==1 ) {}
      DEBUG ((DEBUG_ERROR, "I2cWR wrong ENST value\n"));
      return EFI_TIMEOUT;
    }

  }
}

//
// AdvancedFeaturesBegin
//
/**
  Executes a block of I2C read/write and wait commands. This is intended to enable or configure devices that require multiple data transactions through a I2C bus.
  The command structure consists of entries with a command id, data count, and payload if applicable. The following is an example of a command block:

  static UINT8 SampleCommandBlock[] = {
    BLOCK_COMMAND_I2C_WRITE, 3, 0x01, 0x02, 0x03,    //Write data. The maximum payload is defined by MAX_DATA_COUNT
    BLOCK_COMMAND_WAIT, 10,                          //Wait for 10ms.
    BLOCK_COMMSND_I2C_READ, 1                        //Read 1 byte. This is also limited by MAX_DATA_COUNT
  }

  @param[in] 2cController            the serial controller to be used
  @param[in] SlaveAddress            the address for the device
  @param[in] I2cCommandBlock         pointer to a byte structure with all commands and data
  @param[in] BlockSize               total size of the command structure

  @retval    EFI_INVALID_PARAMETER   signals that a command or data count was invalid
  @retval    EFI_BUFFER_TOO_SMALL    the last command could not be executed since the buffer ended before reading all parameters
  @retval    EFI_SUCCESS             the command were executed without incident
**/

EFI_STATUS
I2cBlockWriteRead(
  IN PCH_SERIAL_IO_CONTROLLER I2cController,
  IN UINT8  SlaveAddress,
  IN UINT8 *I2cCommandBlock,
  IN UINT32 BlockSize
)
{
  UINTN                   Bar0;
  UINT8                   DataBuffer[MAX_DATA_COUNT];
  UINT32                  BlockBufferIndex;
  UINT32                  Index;
  UINT8                   CommandId;
  UINT8                   CommandDataCount;
  EFI_STATUS              Status;

  DEBUG((DEBUG_INFO, "I2C Block Write-Read Start:\n"));
  Bar0 = FindSerialIoBar(I2cController, 0);
  DEBUG((DEBUG_INFO, "Bar0 id = 0x%x \r\n", Bar0));

  Status = EFI_SUCCESS;
  BlockBufferIndex = 0;

  while (BlockBufferIndex + 2 <= BlockSize) {
    ///
    /// Process a command by reading the command and data count
    ///
    CommandId = I2cCommandBlock[BlockBufferIndex++];
    CommandDataCount = I2cCommandBlock[BlockBufferIndex++];
    switch (CommandId) {
    case BLOCK_COMMAND_I2C_WRITE:
      if (CommandDataCount > MAX_DATA_COUNT) {
        DEBUG((DEBUG_ERROR, "Invalid WRITE data count: %d\n", CommandDataCount));
        return EFI_INVALID_PARAMETER;
      }
      if (BlockBufferIndex + CommandDataCount > BlockSize) {
        DEBUG((DEBUG_ERROR, "WRITE buffer overrun: %d\n", CommandDataCount));
        return EFI_BUFFER_TOO_SMALL;
      }
      DEBUG((DEBUG_INFO, "I2C Write %d: ", CommandDataCount));
      for (Index = 0; Index < CommandDataCount; Index++) {
        DataBuffer[Index] = I2cCommandBlock[BlockBufferIndex++];
        DEBUG((DEBUG_INFO, "%x ", DataBuffer[Index]));
      }
      DEBUG((DEBUG_INFO, "\n"));
      Status = I2cWriteRead(Bar0, SlaveAddress, CommandDataCount, DataBuffer, 0, NULL, WAIT_1_SECOND);
      if (Status != EFI_SUCCESS) {
        return Status;
      }
      break;

    case BLOCK_COMMAND_I2C_READ:
      if (CommandDataCount > MAX_DATA_COUNT) {
        DEBUG((DEBUG_ERROR, "Invalid READ data count: %d\n", CommandDataCount));
        return EFI_INVALID_PARAMETER;
      }
      Status = I2cWriteRead(Bar0, SlaveAddress, 0, NULL, CommandDataCount, DataBuffer, WAIT_1_SECOND);
      if (Status != EFI_SUCCESS) {
        return Status;
      }
      DEBUG((DEBUG_INFO, "I2C Read %d: ", CommandDataCount));
      for (Index = 0; Index < CommandDataCount; Index++) {
        DEBUG((DEBUG_INFO, "%x ", DataBuffer[Index]));
      }
      DEBUG((DEBUG_INFO, "\n"));
      break;

    case BLOCK_COMMAND_WAIT:
      DEBUG((DEBUG_INFO, "Wait %dms\n", CommandDataCount));
      MicroSecondDelay((UINTN)CommandDataCount * 1000);
      break;

    default:
      DEBUG((DEBUG_ERROR, "Invalid command.\n"));
      return EFI_INVALID_PARAMETER;
    }
  }
  ///
  /// If the buffer index is not at the end of the buffer defined by size, there was a mismatch and potentially an error.
  ///
  if (BlockBufferIndex != BlockSize) {
    DEBUG((DEBUG_ERROR, "There was an error executing the I2C command block. Buffer length mismatch.\n"));
    return EFI_BAD_BUFFER_SIZE;
  }
  DEBUG((DEBUG_INFO, "I2C Block Write-Read Done.\n"));
  return EFI_SUCCESS;
}
//
// AdvancedFeaturesEnd
//
